package com.boyuan.system.utils;

import com.boyuan.system.hik.HCNetSDK;
import com.boyuan.system.websocket.BlockQueue;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.ByteByReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;

/**
 * 视频预览方法
 *
 * @author ply
 * @date 2023-11-20
 */
@Slf4j
@Component
public class VideoPreview {
    //预览回调函数实现
    public static FrealDataCallBack frealDataCallBack;
    //预览句柄
    public static int lPlay = -1;
    //预览是否成功
    public static boolean lPlayStatus = false;
    //预览错误信息
    public static String lPlayErrorMassage = "";

    /**
     * 预览摄像头
     *
     * @param userId    登录成功返回的id
     * @param channelId 通道id
     */
    public static void realPlay(int userId, int channelId) {
        if (userId == -1) {
            log.info("请先登录摄像头");
            lPlayStatus = false;
            lPlayErrorMassage = "请先登录";
            return;
        }
        HCNetSDK.NET_DVR_PREVIEWINFO previewInfo = new HCNetSDK.NET_DVR_PREVIEWINFO();
        previewInfo.read();
        //窗口句柄，从回调取流不显示，一般设置为空
        previewInfo.hPlayWnd = 0;
        //通道号
        previewInfo.lChannel = channelId;
        //0-主码流，1-子码流，2-三码流，3-虚拟码流，以此类推
        previewInfo.dwStreamType = 0;
        //连接方式：0- TCP方式，1- UDP方式，2- 多播方式，3- RTP方式，4- RTP/RTSP，5- RTP/HTTP，6- HRUDP（可靠传输） ，7- RTSP/HTTPS，8- NPQ
        previewInfo.dwLinkMode = 0;
        previewInfo.bBlocked = 1;

        //回调函数定义必须是全局的
        if (frealDataCallBack == null) {
            frealDataCallBack = new FrealDataCallBack();
        }

        //开启预览
        lPlay = PreviewUtil.hcNetSDK.NET_DVR_RealPlay_V40(userId, previewInfo, frealDataCallBack, null);
        log.info("开启预览结果：" + lPlay);
        if (lPlay == -1) {
            int errorCode = PreviewUtil.hcNetSDK.NET_DVR_GetLastError();
            lPlayStatus = false;
            lPlayErrorMassage = "取流失败，错误码：" + errorCode;
        } else {
            log.info("取流成功");
            lPlayStatus = true;
        }

    }

    /**
     * 预览回调方法
     */
    static class FrealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {
        //预览回调   lRealHandle预览句柄回调
        public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
            //log.info("码流数据回调, 数据类型: " + dwDataType + ", 数据长度:" + dwBufSize);
            //播放库解码
            switch (dwDataType) {
                case HCNetSDK.NET_DVR_SYSHEAD: //系统头
                case HCNetSDK.NET_DVR_STREAMDATA://码流数据
                    if ((dwBufSize > 0)) {
                        byte[] outputData = pBuffer.getPointer().getByteArray(0, dwBufSize);
                        try {
                            //将流写入对应的实体
                            writeESH264(outputData, lRealHandle);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
            }
        }

        //多路视频的pes数据进行缓存，知道某一路视频的RTP包开头进入时进行取出返给前端
        Map<String, byte[]> eBytesMap = new HashMap<>();
        /**
         * 提取H264的裸流写入文件
         */
        public void writeESH264(final byte[] outputData, int lRealHandle) throws IOException {
            if (outputData.length == 0) {
                return;
            }
            String playIdStr = String.valueOf(lRealHandle);
            byte[] allEsBytes = null;//当前这个通道的一个Rtp包数据
            if (eBytesMap.containsKey(playIdStr)) {
                allEsBytes = eBytesMap.get(playIdStr);
            } else {
                eBytesMap.put(playIdStr, null);
            }

            if ((outputData[0] & 0xff) == 0x00
                    && (outputData[1] & 0xff) == 0x00
                    && (outputData[2] & 0xff) == 0x01
                    && (outputData[3] & 0xff) == 0xBA) {
                // 一个完整的帧解析完成后将解析的数据放入BlockingQueue,websocket获取后发送给前端
                if (allEsBytes != null && allEsBytes.length > 0) {
                    if (BlockQueue.playToUserIdMap.containsKey(String.valueOf(lRealHandle))) {
                        String userId = BlockQueue.playToUserIdMap.get(String.valueOf(lRealHandle));
                        BlockingQueue blockingQueue = BlockQueue.bqMap.get(userId);
                        try {
                            //将当前的某一路视频通道的上一个Rtp包放到队列中去
                            blockingQueue.put(allEsBytes);
                            BlockQueue.bqMap.put(userId, blockingQueue);
                            allEsBytes = null;
                            //置空当前通道的RTP包，下次回调就是pes包进行取流追加
                            eBytesMap.put(playIdStr, null);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            // 是00 00 01 eo开头的就是视频的pes包
            if ((outputData[0] & 0xff) == 0x00
                    && (outputData[1] & 0xff) == 0x00
                    && (outputData[2] & 0xff) == 0x01
                    && (outputData[3] & 0xff) == 0xE0) {
                // 去掉包头后的起始位置
                int from = 9 + outputData[8] & 0xff;
                int len = outputData.length - 9 - (outputData[8] & 0xff);
                // 获取es裸流
                byte[] esBytes = new byte[len];
                System.arraycopy(outputData, from, esBytes, 0, len);

                if (allEsBytes == null) {
                    allEsBytes = esBytes;
                } else {
                    byte[] newEsBytes = new byte[allEsBytes.length + esBytes.length];
                    System.arraycopy(allEsBytes, 0, newEsBytes, 0, allEsBytes.length);
                    System.arraycopy(esBytes, 0, newEsBytes, allEsBytes.length, esBytes.length);
                    allEsBytes = newEsBytes;
                }
                //当前视频通道的部分包数据进行缓存
                eBytesMap.put(playIdStr, allEsBytes);
            }
        }
    }


}
